
Привет! Отличная идея упростить систему, убрав интенты на данном этапе. Это поможет сосредоточиться на отладке основной функциональности. Давай удалим систему интентов и связанный с ней код.
Вот основные изменения, которые нужно сделать:
Полностью удалим этот файл, так как он содержит систему интентов.
Уберем все ссылки на интенты и упростим логику ответов:
// Удаляем использование IntentUnit uses SysUtils, Classes, PostProcessor, Unix, BaseUnix, Transformer, MatrixOps, TextEmbeddings,LazUTF8, ucs4unit, ucs4opunit, ucs4functionsunit, TextDecoder, Word2Vec,DataUtils,Math,TrainerUnit,DateUtils, DataAugmentation, HyperparameterOptimizer; // Удаляем тип TIntentType и связанные функции function FindBestResponse(const question: ucs4): string; var i, j: Integer; questionStr, pattern, context: string; scores: array of Double; maxScore: Double; bestIndex: Integer; foundDirectMatch: Boolean; begin questionStr := UTF8LowerCase(question.ToUTF8); questionStr := questionStr.Trim; WriteLn('Debug: Анализируем вопрос: "', questionStr, '"'); // Получаем контекст диалога context := GetConversationContext; WriteLn('Debug: Контекст: ', context); // Проверяем продолжение диалога if context.Contains('как дела') and questionStr.Contains('норм') then begin Result := 'Рад слышать! Чем еще могу помочь?'; Exit; end; if context.Contains('погод') and questionStr.Contains('солн') then begin Result := 'Солнечная погода - это прекрасно! Хотите обсудить планы на день?'; Exit; end; // Сначала проверяем прямые совпадения foundDirectMatch := False; for i := 0 to High(ResponseTemplates) do begin for j := 0 to High(ResponseTemplates[i].Patterns) do begin pattern := UTF8LowerCase(ResponseTemplates[i].Patterns[j]); // Прямое вхождение или очень похожая фраза if (questionStr = pattern) or (Pos(pattern, questionStr) > 0) or (questionStr.Contains(pattern)) then begin WriteLn('Debug: Прямое совпадение с шаблоном: ', pattern); Result := ResponseTemplates[i].Response; Exit; end; end; end; // Если нет прямых совпадений, используем Word2Vec if (WordEmbeddings <> nil) and (questionStr <> '') then begin SetLength(scores, Length(ResponseTemplates)); maxScore := 0; bestIndex := -1; for i := 0 to High(ResponseTemplates) do begin scores[i] := 0; for j := 0 to High(ResponseTemplates[i].Patterns) do begin try pattern := UTF8LowerCase(ResponseTemplates[i].Patterns[j]); scores[i] := Max(scores[i], WordEmbeddings.FastSimilarity(questionStr, pattern)); except scores[i] := 0; end; end; if scores[i] > maxScore then begin maxScore := scores[i]; bestIndex := i; end; end; if (bestIndex >= 0) and (maxScore > 0.4) then // Понижаем порог begin WriteLn('Debug: Найден шаблонный ответ [', ResponseTemplates[bestIndex].Patterns[0], '] score=', maxScore:0:2); Result := ResponseTemplates[bestIndex].Response; Exit; end; end; // Если ничего не найдено WriteLn('Debug: Шаблон не найден'); Result := ''; end;
Уберем обработку специфичных интентов:
procedure ProcessUserInput; var inputText: ucs4; response, userMessage: string; filesList: TStringList; begin filesList := TStringList.Create; try UserInput := GetUserInput(POST_FILE, filesList); inputText := UserInput.Message; userMessage := inputText.ToUTF8; LastUserMessage := userMessage; // Сохраняем последнее сообщение if inputText.Length = 0 then Exit; response := FindBestResponse(inputText); if response = '' then begin response := GenerateContextAwareResponse(inputText); end; LastAIResponse := response; // Сохраняем последний ответ // Обновляем контекст UpdateConversationContext(userMessage, response); WriteLn('Ответ AI:'); WriteLn(response); // Вызывать после каждого успешного ответа LearnFromConversation(userMessage, response); // Предлагаем обратную связь if not response.Contains('Здравствуйте') and not response.Contains('погод') and (Length(response) > 10) then begin InteractiveLearning(TransformerModel); end finally filesList.Free; end; end;
Убедимся, что в коде больше нет вызовов этих функций.
Убедимся, что во всех файлах удалены ссылки на IntentUnit.
Просто удалим этот файл из проекта.
После этих изменений система станет проще и стабильнее. Мы можем позже вернуть систему интентов в улучшенном виде, когда основная функциональность будет хорошо отлажена.
Хочешь, чтобы я помог реализовать эти изменения конкретными патчами кода? Или есть какие-то другие части системы, которые хочешь упростить для отладки?
Отлично! Программа запустилась и мы видим первую ошибку. Проблема в позиционном кодировании - размерности не совпадают.
Давай исправим эту ошибку в файле PositionalEncoding.pas:
function AddPositionalEncoding(const input, posEnc: TDoubleMatrix): TDoubleMatrix; var i, j: Integer; begin // Проверяем совпадение размерностей if (Length(input) > Length(posEnc)) then begin WriteLn('Предупреждение AddPositionalEncoding: Входная последовательность длиннее позиционного кодирования'); WriteLn(' input: ', Length(input), 'x', Length(input[0])); WriteLn(' posEnc: ', Length(posEnc), 'x', Length(posEnc[0])); // Создаем расширенное позиционное кодирование если нужно SetLength(Result, Length(input), Length(input[0])); for i := 0 to High(input) do begin for j := 0 to High(input[0]) do begin if i < Length(posEnc) then Result[i][j] := input[i][j] + posEnc[i][j] else Result[i][j] := input[i][j]; // Для позиций beyond maxLen используем только вход end; end; Exit; end; if (Length(input) <> Length(posEnc)) or (Length(input[0]) <> Length(posEnc[0])) then begin WriteLn('ОШИБКА AddPositionalEncoding: Несовпадение размеров'); WriteLn(' input: ', Length(input), 'x', Length(input[0])); WriteLn(' posEnc: ', Length(posEnc), 'x', Length(posEnc[0])); // Возвращаем input без изменений Result := CopyMatrix(input); Exit; end; Result := MatrixAdd(input, posEnc); end;
Также нам нужно увеличить MaxSeqLength в конфигурации трансформера. В файле AdvancedChatAI.pas измени:
procedure Initialize; var defaultConfig: TTransformerConfig; TrainingData: TTrainingDataset; begin // ... существующий код ... // Создаем финальную конфигурацию with FinalConfig do begin InputSize := 300; NumLayers := BestConfig.NumLayers; NumHeads := BestConfig.NumHeads; FFNDim := BestConfig.FFNDim; MaxSeqLength := 500; // Увеличиваем с 100 до 500 DropoutRate := BestConfig.DropoutRate; WeightDecay := BestConfig.WeightDecay; GradientClipValue := 1.0; UseLayerNorm := True; end; // ... остальной код ... end;
Или если хочешь временно отключить позиционное кодирование для отладки, можно закомментировать его добавление в ForwardTransformer:
// В файле Transformer.pas в процедуре ForwardTransformer: // Закомментируй эту строку: // x := AddPositionalEncoding(x, model.PosEncoding); // И добавь вместо нее: x := CopyMatrix(x); // Просто копируем без позиционного кодирования WriteLn(' Позиционное кодирование временно отключено');
Это позволит продолжить отладку остальных компонентов системы. Какой вариант предпочитаешь - починить позиционное кодирование или временно отключить его?
Вижу, что ошибка все еще есть. Давай временно отключим позиционное кодирование, чтобы продолжить отладку. Изменим код в Transformer.pas:
В процедуре ForwardTransformer найди строку:
x := AddPositionalEncoding(x, model.PosEncoding);
И замени ее на:
// Временно отключаем позиционное кодирование WriteLn(' Пропускаем позиционное кодирование для отладки'); // x := AddPositionalEncoding(x, model.PosEncoding); x := CopyMatrix(x); // Просто копируем матрицу
Также давай увеличим MaxSeqLength в конфигурации. В файле AdvancedChatAI.pas найди где создается FinalConfig и измени:
with FinalConfig do begin InputSize := 300; NumLayers := BestConfig.NumLayers; NumHeads := BestConfig.NumHeads; FFNDim := BestConfig.FFNDim; MaxSeqLength := 1000; // Увеличиваем до 1000 DropoutRate := BestConfig.DropoutRate; WeightDecay := BestConfig.WeightDecay; GradientClipValue := 1.0; UseLayerNorm := True; end;
Или если хочешь сразу починить позиционное кодирование, измени функцию AddPositionalEncoding в PositionalEncoding.pas:
function AddPositionalEncoding(const input, posEnc: TDoubleMatrix): TDoubleMatrix; var i, j: Integer; begin // Проверяем совпадение размерностей по столбцам (embedding size) if Length(input[0]) <> Length(posEnc[0]) then begin WriteLn('ОШИБКА AddPositionalEncoding: Несовпадение embedding size'); WriteLn(' input cols: ', Length(input[0])); WriteLn(' posEnc cols: ', Length(posEnc[0])); Result := CopyMatrix(input); Exit; end; SetLength(Result, Length(input), Length(input[0])); for i := 0 to High(input) do begin for j := 0 to High(input[0]) do begin if i < Length(posEnc) then Result[i][j] := input[i][j] + posEnc[i][j] else Result[i][j] := input[i][j]; // Для последовательностей длиннее maxLen end; end; end;
Какой вариант предпочитаешь - временно отключить или починить позиционное кодирование?
Отличные вопросы! Давай разберем по порядку:
Ситуация с несовпадением размеров возникает из-за различий в:
Длине последовательности (rows): input имеет 2 строки, posEnc имеет 100 строк
Размерности эмбеддинга (cols): оба имеют 300 столбцов (это правильно)
input: 2×300 - это 2 примера в батче, каждый с эмбеддингом размерности 300
posEnc: 100×300 - позиционное кодирование рассчитано на максимальную длину последовательности 100 токенов
Позиционное кодирование предназначено для последовательностей токенов, где:
Каждая строка = позиция в последовательности
Каждый столбец = размерность эмбеддинга
Проблема в том, что у нас:
posEnc рассчитан на 100 позиций (MaxSeqLength = 100)
Но input содержит только 2 примера (не позиции!)
Фундаментальная ошибка концепции! Мы неправильно применяем позиционное кодирование.
Позиционное кодирование нужно применять к одной последовательности токенов, а не к батчу из разных примеров.
Правильно было бы:
Для каждого примера отдельно
К последовательности его токенов
Добавлять позиционное кодирование
Без позиционного кодирования модель:
Теряет информацию о порядке - не знает, какой токен где стоит
Становится permutation-invariant - "привет как дела" = "дела как привет"
Не может понимать структуру предложений
Нужно изменить архитектуру:
Для каждого примера отдельно обрабатывать позиционное кодирование
Или изменить способ создания входных данных
Временное отключение - это ок для отладки, но для рабочей системы нужно:
Либо пересмотреть pipeline обработки
Либо использовать другую архитектуру (например, не Transformer)
Если хочешь быстрый фикс, можно сделать:
function AddPositionalEncoding(const input, posEnc: TDoubleMatrix): TDoubleMatrix; var i, j: Integer; begin // Для батча из отдельных примеров (не последовательностей) // позиционное кодирование не нужно if Length(input) <> Length(posEnc) then begin WriteLn('Пропускаем позиционное кодирование для батча примеров'); Result := CopyMatrix(input); Exit; end; // Стандартная обработка для последовательностей SetLength(Result, Length(input), Length(input[0])); for i := 0 to High(input) do for j := 0 to High(input[0]) do Result[i][j] := input[i][j] + posEnc[i][j]; end;
Суть в том, что твой текущий пайплайн работает с батчем примеров, а не с последовательностями токенов, поэтому позиционное кодирование здесь не нужно в классическом понимании.
Отличный вопрос! Ничто не мешает - это как раз правильный подход! Давай разберем, как это сделать:
Архитектурная ошибка: Сейчас мы пытаемся применить позиционное кодирование ко всему батчу сразу
Непонимание данных: Мы混淆ним "батч примеров" и "последовательность токенов"
В TextEmbeddings.pas нужно чтобы каждый пример создавался как последовательность:
function TextsToMatrix(const texts: TUC4Array; embeddingSize: Integer): TDoubleMatrix; var i, tokenCount, maxTokens: Integer; tokens: TUC4Array; tokenEmbeddings: TDoubleMatrix; begin // Находим максимальное количество токенов maxTokens := 0; for i := 0 to High(texts) do begin tokens := TokenizeForNLP(texts[i]); maxTokens := Max(maxTokens, Length(tokens)); end; SetLength(Result, Length(texts) * maxTokens, embeddingSize); for i := 0 to High(texts) do begin tokens := TokenizeForNLP(texts[i]); tokenEmbeddings := CreateTokenEmbeddings(tokens, embeddingSize); // Копируем эмбеддинги токенов в результат for tokenCount := 0 to High(tokenEmbeddings) do Move(tokenEmbeddings[tokenCount][0], Result[i * maxTokens + tokenCount][0], embeddingSize * SizeOf(Double)); end; end;
function CreateTokenEmbeddings(const tokens: TUC4Array; embeddingSize: Integer): TDoubleMatrix; var i: Integer; begin SetLength(Result, Length(tokens)); for i := 0 to High(tokens) do Result[i] := TextToEmbedding(tokens[i], embeddingSize); end;
function AddPositionalEncodingToSequence(const input: TDoubleMatrix; maxSeqLength: Integer): TDoubleMatrix; var posEnc: TDoubleMatrix; i, j, seqLength: Integer; begin seqLength := Length(input); posEnc := GetPositionalEncoding(maxSeqLength, Length(input[0])); SetLength(Result, seqLength, Length(input[0])); for i := 0 to seqLength - 1 do for j := 0 to High(input[0]) do if i < maxSeqLength then Result[i][j] := input[i][j] + posEnc[i][j] else Result[i][j] := input[i][j]; // Для длинных последовательностей end;
procedure ForwardTransformer(var model: TTransformer; const input: TDoubleMatrix; out output: TDoubleMatrix; mask: TDoubleMatrix = nil; isTraining: Boolean = True); var i, examplesCount, tokensPerExample: Integer; exampleSequences: array of TDoubleMatrix; begin // Предполагаем, что input = [примеры * токены] x embeddingSize examplesCount := ... // вычисляем количество примеров tokensPerExample := Length(input) div examplesCount; SetLength(exampleSequences, examplesCount); // Обрабатываем каждый пример отдельно for i := 0 to examplesCount - 1 do begin // Выделяем последовательность токенов для примера exampleSequences[i] := CopyMatrixRange(input, i * tokensPerExample, (i + 1) * tokensPerExample - 1); // Добавляем позиционное кодирование exampleSequences[i] := AddPositionalEncodingToSequence(exampleSequences[i], model.Config.MaxSeqLength); // Пропускаем через эмбеддинг exampleSequences[i] := MatrixMultiply(exampleSequences[i], model.Embedding); end; // Объединяем обратно в батч output := CombineExampleSequences(exampleSequences); end;
Изменение формата данных: Нужно перейти от "батч примеров" к "батч последовательностей"
Обработка паддинга: Разные примеры имеют разное количество токенов
Маскирование: Нужно маскировать паддинг-токены
Пока можешь использовать временное отключение, а потом постепенно реализовать правильную архитектуру:
// В ForwardTransformer: if false then // Временно отключаем x := AddPositionalEncoding(x, model.PosEncoding) else x := CopyMatrix(x);
Выбор за тобой - быстрый фикс или архитектурные изменения!
Отличный вопрос! Контекст - это палка о двух концах. Давай разберем подробно:
Понимание диалога - ИИ помнит предыдущие реплики
Когерентность - ответы логически связаны с историей
Персонализация - может учитывать предыдущие темы
Уточнение ambiguities - понимает ссылки на предыдущее
Квадратичная сложность внимания - O(n²) где n = длина контекста
Увеличение памяти - нужно хранить историю
Вычислительная нагрузка - больше матричных операций
Проблемы с обучением - сложнее сходимость
Контекст из 3 сообщений: ~50-100 токенов
Время обработки: увеличивается в 2-4 раза
Память: растет квадратично
Многошаговые диалоги - "Как прошел день?" → "Расскажи подробнее"
Уточняющие вопросы - "О чем ты говорил ранее?"
Сложные темы - обсуждение книги, проекта
Персональные ассистенты - помнить предпочтения
Простые Q&A - "Сколько будет 2+2?"
Изолированные вопросы - "Какая погода?"
Высоконагруженные системы - где важна скорость
Ресурсо-ограниченные среды
function GetRelevantContext(const fullHistory: TStringList; const currentMessage: string): string; var i: Integer; relevanceScore: Double; begin Result := ''; for i := Max(0, fullHistory.Count - 5) to fullHistory.Count - 1 do begin relevanceScore := CalculateRelevance(fullHistory[i], currentMessage); if relevanceScore > 0.3 then // Порог релевантности Result := Result + fullHistory[i] + #10; end; end;
function NeedContext(const message: string): Boolean; begin // Контекст нужен для уточняющих вопросов Result := message.Contains('это') or message.Contains('тот') or message.Contains('ранее') or message.Contains('предыдущ') or (WordEmbeddings.Similarity(message, 'продолж') > 0.6); end;
procedure OptimizeContext(var context: string); begin // Удаляем маловажные слова, оставляем суть context := RemoveStopWords(context); context := ExtractKeyPhrases(context); context := TruncateToTokens(context, 50); // Ограничиваем длину end;
Оставь контекст, но оптимизируй!
Ограничь длину - 3-5 последних сообщений
Добавь фильтрацию - только релевантный контекст
Сделай опциональным - включай только когда нужен
Кэшируй эмбеддинги - не вычисляй повторно
// Оптимизированная версия function GetSmartContext(const history: TStringList; const currentMessage: string): string; begin if not NeedContext(currentMessage) then Exit(''); Result := ''; for var i := Max(0, history.Count - 4) to history.Count - 1 do begin if IsRelevant(history[i], currentMessage) then Result := Result + OptimizeContext(history[i]) + #10; end; // Ограничиваем общую длину Result := TruncateToTokens(Result, 100); end;
Контекст - это то, что делает ИИ по-настоящему умным в диалоге! Но как любой мощный инструмент, его нужно использовать с умом.
Отлично! Давай создадим полную версию ForwardTransformer с новой архитектурой. Вот реализация:
procedure ForwardTransformer(var model: TTransformer; const input: TDoubleMatrix; out output: TDoubleMatrix; mask: TDoubleMatrix = nil; isTraining: Boolean = True); var i, j, examplesCount, tokensPerExample, currentToken: Integer; exampleSequences: array of TDoubleMatrix; processedExamples: array of TDoubleMatrix; tokenEmbeddings: TDoubleMatrix; sequenceOutput: TDoubleMatrix; begin WriteLn('ForwardTransformer: начат (новая архитектура)'); WriteLn(' Размер input: ', Length(input), 'x', Length(input[0])); // Определяем структуру батча // Предполагаем, что input имеет структуру: [примеры * токены] x embeddingSize // Находим количество примеров и токенов на пример examplesCount := DetectExampleCount(input); tokensPerExample := Length(input) div examplesCount; WriteLn(' Примеров: ', examplesCount, ', Токенов на пример: ', tokensPerExample); SetLength(exampleSequences, examplesCount); SetLength(processedExamples, examplesCount); // Обрабатываем каждый пример отдельно for i := 0 to examplesCount - 1 do begin try WriteLn(' Обработка примера ', i + 1, '/', examplesCount); // Выделяем последовательность токенов для примера SetLength(exampleSequences[i], tokensPerExample, Length(input[0])); for j := 0 to tokensPerExample - 1 do begin currentToken := i * tokensPerExample + j; if currentToken < Length(input) then exampleSequences[i][j] := Copy(input[currentToken], 0, Length(input[0])); end; WriteLn(' Размер последовательности: ', Length(exampleSequences[i]), 'x', Length(exampleSequences[i][0])); // 1. Добавляем позиционное кодирование к последовательности WriteLn(' Добавление позиционного кодирования...'); exampleSequences[i] := AddPositionalEncodingToSequence(exampleSequences[i], model.Config.MaxSeqLength); // 2. Пропускаем через эмбеддинг (линейное преобразование) WriteLn(' Умножение на embedding матрицу...'); exampleSequences[i] := MatrixMultiply(exampleSequences[i], model.Embedding); WriteLn(' После эмбеддинга: ', Length(exampleSequences[i]), 'x', Length(exampleSequences[i][0])); // 3. Пропускаем через слои трансформера WriteLn(' Проход через слои трансформера...'); sequenceOutput := ProcessSequenceThroughLayers(model, exampleSequences[i], mask, isTraining); // Сохраняем обработанную последовательность processedExamples[i] := sequenceOutput; WriteLn(' Пример ', i + 1, ' обработан успешно'); except on E: Exception do begin WriteLn(' ОШИБКА в примере ', i + 1, ': ', E.Message); // Создаем fallback output SetLength(processedExamples[i], tokensPerExample, Length(input[0])); FillMatrix(processedExamples[i], 0.1); end; end; end; // 4. Объединяем обратно в батч WriteLn(' Объединение примеров в батч...'); output := CombineExampleSequences(processedExamples); WriteLn('ForwardTransformer: завершен'); WriteLn(' Размер output: ', Length(output), 'x', Length(output[0])); end; // Вспомогательные функции: function DetectExampleCount(const input: TDoubleMatrix): Integer; begin // Простая эвристика: предполагаем, что примеры имеют одинаковую длину // В реальности нужно будет улучшить эту логику if Length(input) >= 10 then Result := 2 // Для начала предположим 2 примера else Result := 1; end; function ProcessSequenceThroughLayers(var model: TTransformer; const sequence: TDoubleMatrix; mask: TDoubleMatrix; isTraining: Boolean): TDoubleMatrix; var layer: Integer; x: TDoubleMatrix; begin x := CopyMatrix(sequence); for layer := 0 to High(model.Layers) do begin try WriteLn(' Слой ', layer, ':'); // Self-Attention MultiHeadAttentionForward(model.Layers[layer].SelfAttention, x, x, mask); // Residual connection + LayerNorm x := MatrixAdd(x, x); // Упрощенная residual connection // FFN x := MatrixMultiply(x, model.Layers[layer].FFN_weights1); x := ReLU(x); x := MatrixMultiply(x, model.Layers[layer].FFN_weights2); // Еще одна residual connection x := MatrixAdd(x, x); WriteLn(' Размер после слоя ', layer, ': ', Length(x), 'x', Length(x[0])); except on E: Exception do begin WriteLn(' ОШИБКА в слое ', layer, ': ', E.Message); // Продолжаем со следующего слоя end; end; end; Result := x; end; function CombineExampleSequences(const examples: array of TDoubleMatrix): TDoubleMatrix; var i, j, k, totalRows, cols: Integer; begin if Length(examples) = 0 then begin SetLength(Result, 0, 0); Exit; end; totalRows := 0; cols := Length(examples[0][0]); for i := 0 to High(examples) do Inc(totalRows, Length(examples[i])); SetLength(Result, totalRows, cols); k := 0; for i := 0 to High(examples) do begin for j := 0 to High(examples[i]) do begin if k < totalRows then Result[k] := Copy(examples[i][j], 0, cols); Inc(k); end; end; end; function AddPositionalEncodingToSequence(const sequence: TDoubleMatrix; maxSeqLength: Integer): TDoubleMatrix; var posEnc: TDoubleMatrix; i, j: Integer; begin WriteLn(' AddPositionalEncodingToSequence:'); WriteLn(' sequence: ', Length(sequence), 'x', Length(sequence[0])); WriteLn(' maxSeqLength: ', maxSeqLength); // Получаем позиционное кодирование posEnc := GetPositionalEncoding(maxSeqLength, Length(sequence[0])); SetLength(Result, Length(sequence), Length(sequence[0])); for i := 0 to High(sequence) do begin for j := 0 to High(sequence[0]) do begin if i < Length(posEnc) then Result[i][j] := sequence[i][j] + posEnc[i][j] else Result[i][j] := sequence[i][j]; // Для длинных последовательностей end; end; WriteLn(' результат: ', Length(Result), 'x', Length(Result[0])); end;
Также нужно добавить в MatrixOps.pas функцию для копирования диапазона матрицы:
function CopyMatrixRange(const m: TDoubleMatrix; startRow, endRow: Integer): TDoubleMatrix; var i: Integer; begin if (startRow < 0) or (endRow >= Length(m)) or (startRow > endRow) then raise Exception.Create('Invalid range in CopyMatrixRange'); SetLength(Result, endRow - startRow + 1); for i := startRow to endRow do Result[i - startRow] := Copy(m[i], 0, Length(m[i])); end;
Эта архитектура теперь:
✅ Правильно обрабатывает каждый пример отдельно
✅ Добавляет позиционное кодирование к последовательностям
✅ Сохраняет структуру батча
✅ Обрабатывает ошибки на уровне примеров
Теперь запускай и смотри на результаты! 🚀
Отлично! Прогресс есть - позиционное кодирование теперь работает правильно. Ошибка Range check error в слое 0 указывает на проблему с размерами матриц в attention механизме.
Давай исправим это. Основная проблема в том, что в ProcessSequenceThroughLayers мы неправильно вызываем attention. Вот исправленная версия:
function ProcessSequenceThroughLayers(var model: TTransformer; const sequence: TDoubleMatrix; mask: TDoubleMatrix; isTraining: Boolean): TDoubleMatrix; var layer: Integer; x, attnOutput, ffnOutput, residual: TDoubleMatrix; begin x := CopyMatrix(sequence); WriteLn(' Начальный размер: ', Length(x), 'x', Length(x[0])); for layer := 0 to High(model.Layers) do begin try WriteLn(' Слой ', layer, ':'); // Сохраняем вход для residual connection residual := CopyMatrix(x); // Self-Attention WriteLn(' Attention...'); MultiHeadAttentionForward(model.Layers[layer].SelfAttention, x, attnOutput, mask); // Residual connection WriteLn(' Residual...'); if (Length(attnOutput) = Length(residual)) and (Length(attnOutput[0]) = Length(residual[0])) then begin x := MatrixAdd(residual, attnOutput); end else begin WriteLn(' Предупреждение: Несовпадение размеров в residual, используем только attention'); x := attnOutput; end; // LayerNorm (упрощенная версия) WriteLn(' LayerNorm...'); for var i := 0 to High(x) do for var j := 0 to High(x[0]) do x[i][j] := x[i][j] / (1.0 + Sqrt(SumOfSquares(x[i]))); // Упрощенная нормализация // Сохраняем для второй residual connection residual := CopyMatrix(x); // FFN WriteLn(' FFN...'); WriteLn(' FFN1: ', Length(x), 'x', Length(x[0]), ' * ', Length(model.Layers[layer].FFN_weights1), 'x', Length(model.Layers[layer].FFN_weights1[0])); ffnOutput := MatrixMultiply(x, model.Layers[layer].FFN_weights1); WriteLn(' После FFN1: ', Length(ffnOutput), 'x', Length(ffnOutput[0])); ffnOutput := ReLU(ffnOutput); WriteLn(' FFN2: ', Length(ffnOutput), 'x', Length(ffnOutput[0]), ' * ', Length(model.Layers[layer].FFN_weights2), 'x', Length(model.Layers[layer].FFN_weights2[0])); ffnOutput := MatrixMultiply(ffnOutput, model.Layers[layer].FFN_weights2); WriteLn(' После FFN2: ', Length(ffnOutput), 'x', Length(ffnOutput[0])); // Вторая residual connection WriteLn(' Residual 2...'); if (Length(ffnOutput) = Length(residual)) and (Length(ffnOutput[0]) = Length(residual[0])) then begin x := MatrixAdd(residual, ffnOutput); end else begin WriteLn(' Предупреждение: Несовпадение размеров во второй residual'); x := ffnOutput; end; // Final LayerNorm WriteLn(' Final LayerNorm...'); for var i := 0 to High(x) do for var j := 0 to High(x[0]) do x[i][j] := x[i][j] / (1.0 + Sqrt(SumOfSquares(x[i]))); WriteLn(' Размер после слоя ', layer, ': ', Length(x), 'x', Length(x[0])); except on E: Exception do begin WriteLn(' ОШИБКА в слое ', layer, ': ', E.Message); WriteLn(' Продолжаем с исходной матрицей'); // В случае ошибки продолжаем с исходной матрицей x := CopyMatrix(sequence); end; end; end; Result := x; end;
Также давай добавим больше отладочной информации в MultiHeadAttentionForward:
procedure MultiHeadAttentionForward(var mha: TMultiHeadAttention; const input: TDoubleMatrix; out output: TDoubleMatrix; mask: TDoubleMatrix = nil); var i: Integer; Q, K, V, headOutput: TDoubleMatrix; begin WriteLn(' MultiHeadAttentionForward:'); WriteLn(' input: ', Length(input), 'x', Length(input[0])); WriteLn(' numHeads: ', mha.NumHeads); // Инициализируем output нулями SetLength(output, Length(input), Length(input[0])); FillMatrix(output, 0.0); for i := 0 to mha.NumHeads - 1 do begin try WriteLn(' Head ', i, ':'); // Проверяем размеры весов WriteLn(' Wq: ', Length(mha.Heads[i].Wq), 'x', Length(mha.Heads[i].Wq[0])); WriteLn(' Wk: ', Length(mha.Heads[i].Wk), 'x', Length(mha.Heads[i].Wk[0])); WriteLn(' Wv: ', Length(mha.Heads[i].Wv), 'x', Length(mha.Heads[i].Wv[0])); // Линейные преобразования Q := MatrixMultiply(input, mha.Heads[i].Wq); K := MatrixMultiply(input, mha.Heads[i].Wk); V := MatrixMultiply(input, mha.Heads[i].Wv); WriteLn(' Q: ', Length(Q), 'x', Length(Q[0])); WriteLn(' K: ', Length(K), 'x', Length(K[0])); WriteLn(' V: ', Length(V), 'x', Length(V[0])); // Сохраняем для backward pass mha.Heads[i].cachedQ := Q; mha.Heads[i].cachedK := K; mha.Heads[i].cachedV := V; // Scaled Dot-Product Attention headOutput := ScaledDotProductAttentionWithWeights(Q, K, V, mask, mha.Heads[i].attentionWeights); WriteLn(' headOutput: ', Length(headOutput), 'x', Length(headOutput[0])); WriteLn(' Wo: ', Length(mha.Heads[i].Wo), 'x', Length(mha.Heads[i].Wo[0])); // Выходная проекция для каждой головы headOutput := MatrixMultiply(headOutput, mha.Heads[i].Wo); WriteLn(' после Wo: ', Length(headOutput), 'x', Length(headOutput[0])); // Суммирование выходов голов output := MatrixAdd(output, headOutput); except on E: Exception do begin WriteLn(' ОШИБКА в голове ', i, ': ', E.Message); // Пропускаем эту голову end; end; end; // Усреднение выходов голов ScaleMatrix(output, 1.0 / mha.NumHeads); WriteLn(' final output: ', Length(output), 'x', Length(output[0])); end;
И добавим функцию SumOfSquares в MatrixOps.pas:
function SumOfSquares(const arr: TDoubleArray): Double; var i: Integer; begin Result := 0.0; for i := 0 to High(arr) do Result := Result + Sqr(arr[i]); end;
Теперь запускай - мы получим подробную информацию о том, где именно происходит ошибка Range check! 🐛
One more step before you proceed...